/*
Uninclude: Tool for detecting unused #included files in C and C++ code.
    Copyright (C) 2012  Daniel Gutson, FuDePAN

    Uninclude is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Uninclude is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Uninclude.  If not, see <http://www.gnu.org/licenses/>.

    This is the main file.
*/

#include <memory>
#include "uninclude/cpp_parser.h"
#include "uninclude/file_tree.h"
#include "uninclude/programs_comparer.h"
#include "uninclude/tmp_file.h"
#include "uninclude/dir_utils.h"

class Parser2TreeListener : public CppListenerFixer::Listener
{
    FilesTree* tree;
    size_t systemDepth;

    virtual void setRoot(const string& rootName, size_t /*line*/)
    {
        assert(tree == NULL);
        tree = new FilesTree(removeQuotes(rootName));
    }

    virtual void pushInclude(const string& newName, size_t line, bool systemInclude)
    {
        if (systemInclude)
            ++systemDepth;

        if (systemDepth <= 1)
            tree->pushFile(removeQuotes(newName), line);
    }

    virtual void popInclude(const string& /*oldName*/, size_t /*line*/)
    {
        if (systemDepth <= 1)
            tree->popFile();

        if (systemDepth > 0)
            --systemDepth;
    }

public:
    Parser2TreeListener() : tree(NULL), systemDepth(0) {}

    FilesTree* getTree() const
    {
        return tree;
    }
};

static FilesTree* parseInput(const string& file)
{
    ifstream ifile(file.c_str());

    if (!ifile)
        throw "Preprocessed output not generated";

    Parser2TreeListener parser2treeListener;
    CppListenerFixer parserFixer(&parser2treeListener);
    CppParser parser(&parserFixer);
    string line;

    while (getline(ifile, line))
        parser.parseLine(line);

    return parser2treeListener.getTree();
}

class NodeAPI_Impl : public ProgramsComparer
{
    virtual void reportIncludeNotNeeded(const string& fileName, const string& includeName, size_t line)
    {
        cout << includeName << " included from " << fileName << ":" << line << " seems not used." << endl;
    }

    virtual void reportIncludeNotStandAlone(const string& includeName)
    {
        cout << "Warnig: " << includeName << " does not compile by its own." << endl;
    }
public:
    NodeAPI_Impl(const string& originalName, const string& tmpName, CompilerDriver& compiler, BinDiffDriver& diff)
        : ProgramsComparer(originalName, tmpName, compiler, diff)
    {}
};

static void showHelp();
static void addOptions(CompilerDriver& compiler, int argc, char* argv[]);

enum CmdLineOptions
{
    PROGRAM_NAME,   // "./uninclude"
    CPP_FILE,
    FIRST_OPTION
};

int main(int argc, char* argv[])
{
    int ret = EXIT_FAILURE;

    if (argc < 2)
    {
        cerr << "Invalid arguments." << endl;
        showHelp();
    }
    else
    {
        try
        {
            CompilerDriver compiler;
            BinDiffDriver diff;
            TmpFile preprocessorOutput("preproc");

            addOptions(compiler, argc - FIRST_OPTION, argv + FIRST_OPTION);

            compiler.preprocess(argv[CPP_FILE], preprocessorOutput.getName());

            const auto_ptr<FilesTree> tree(parseInput(preprocessorOutput.getName()));

            if (tree.get() == NULL)
                throw "Error in generated preprocessor output.";

            NodeAPI_Impl api_impl(tree->originalName(), tree->tmpName(), compiler, diff);

            tree->traverseAndCommentIncludes(&api_impl);
            ret = EXIT_SUCCESS;
        }
        catch(const char* msg)
        {
            cerr << msg << endl;
        }
    }
    return ret;
}

void addOptions(CompilerDriver& compiler, int argc, char* argv[])
{
    for( int i=0; i<argc; ++i)
        compiler.addOption(argv[i]);
}

void showHelp()
{
    cout << "usage: uninclude inputfile [compiler options]" << endl;
}

